iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0

每天的專案會同步到 GitLab 上,可以前往 GitLab
查看,有興趣的朋友歡迎留言 or 來信討論,我的信箱是 nickchen1998@gmail.com

什麼是 metadata?

metadata 是一種描述資料的資訊,通常會包含一些關於資料的描述,例如: 檔案的大小、檔案的創建時間、檔案的修改時間等等,
Day10 的時候,我們有使用到 PyMuPDFLoader 這個工具,
這個工具會協助我們進行 PDF 的解檔,解檔的同時也會順便讀取該檔案的 metadata,我們可以看一下下圖中印出的 metadata 的資訊:

metadata

為什麼我們需要 metadata?

首先我們來分析一下,影響查詢段落的準確度的因素大概分為以下幾種:

  • 段落的長度
  • 段落的內容
  • 段落的結構
  • 段落的語義
  • 段落的相似度
  • 段落的數量

而前面四種,都可以使用前兩天提到的 Chunking Strategy 來解決,而 metadata 則是可以幫助我們解決後兩種問題,

前面提到,metadata 是一種描述資料的資訊,這些資訊可以幫助我們更好的管理資料,例如: 我們可以透過 metadata 來查詢檔案的創建時間,
或是可以藉由插入一些足以識別指定檔案的資訊,來加速、限縮我們在查詢的時候所需的時間以及提升準確度,至於相似度的部分,則是必須要等到
檔案數量上升後,才會有比較明確的體悟。

情境問題假設

我們同樣以勞動基準法來做範例展示,首先我們先假定一個要解決的問題,例如:「我想針對勞基法第三頁中的內容做搜尋」,因此我們先來看一下第三頁適合問
什麼樣的問題。

3-11

這是在勞基法第三頁中的某一個段落,我們先假設我們等等要問的問題是「我可以因為不喜歡某個勞工而終止勞動契約嗎?」,
這邊強調,假設情境是為了讓大家理解該怎麼樣使用 metadata 來解決問題, 實際應用可以觀察一下要插入什麼樣的 metadata 來協助你的系統進行查詢。

插入段落內文

由於免費版 Pinecone 只能建立一組索引庫,為了避免每次效果混淆,每次都會把資料清除後再重新建立索引庫。

有了情境假設後,我們就趕緊來實作,讓我們看一下下方的程式碼:

下方的程式碼當中,我們直接將 spliter 與 loader 直接進行合併使用,就不像前幾天為了 demo 而拆開來個別使用。

from env_settings import EnvSettings, BASE_DIR
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_pinecone import PineconeVectorStore
from pinecone import Pinecone
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 讀取並切割 PDF 文件
loader = PyMuPDFLoader(file_path=BASE_DIR / "勞動基準法.pdf")
documents = loader.load_and_split(
    text_splitter=RecursiveCharacterTextSplitter(
        chunk_size=300,
        chunk_overlap=50
    )
)

# 寫入向量
env_settings = EnvSettings()
embedding = OpenAIEmbeddings(
    openai_api_key=env_settings.OPENAI_API_KEY
)
vector_store = PineconeVectorStore(
    index=Pinecone(api_key=env_settings.PINECONE_API_KEY).Index("ithelp"),
    embedding=embedding
)
vector_store.add_documents(documents=documents)

由於使用 LangChain 內建的解檔器的時候,他很貼心的協助我們將 page 作為 metadata 一併生成,因此這邊的 page 我們就不需要另外插入,
眼尖的同學可以發現,在上一個步驟可以發現 metadata 印出來後是一個 dict,因此我們可以透過修改 dict 來插入我們想要的 metadata,
讓我們看一下下方資料被插入後的截圖,確保我們有成功將頁數資訊插入進去:

vectordb

查詢段落內文

每個 vectordb 搜尋 metadata 的方式都不同,本處範例以 Pinecone 為例。
在 Pinecone 當中,我們可以透過 filter 來進行 metadata 的搜尋,這邊我們使用 page 來進行搜尋。

話不多說,我們趕快來看一下下方程式碼:

from env_settings import EnvSettings
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_pinecone import PineconeVectorStore
from pinecone import Pinecone


env_settings = EnvSettings()
embedding = OpenAIEmbeddings(
    openai_api_key=env_settings.OPENAI_API_KEY
)
vector_store = PineconeVectorStore(
    index=Pinecone(api_key=env_settings.PINECONE_API_KEY).Index("ithelp"),
    embedding=embedding
)

# 提問並檢索段落
question = "我可以因為不喜歡某個勞工而終止勞動契約嗎?"
documents = vector_store.similarity_search(
    question,
    k=2,
    filter={"page": 2}  # 因為頁數從 0 開始,因此這裡是第 3 頁
)
for idx, doc in enumerate(documents):
    print(f"----第 {doc.metadata.get('page')} 頁的第 {idx + 1} 筆資料----")
    print(doc.page_content)

下圖可以看到,我們成功透過 metadata 來搜尋到我們想要的段落:

page3_search

比較一下

如同關聯式資料庫,metadata 在向量資料庫當中,也是作為一種欄位存在,我們同樣也可以針對個別的 metadata 建立索引,
也如同關聯式資料庫,適當的索引可以幫助我們提升查詢的效率,然而過多的索引也會導致效能的下降,或是當今天資料量不夠的時候,
多加了一些條件進去反而會降低查詢的速度,因此我們在設計使用 metadata 的時候,一定要謹慎的思考。

下方圖片當中,我們可以看到使用 filter 的查詢秒數,反而比沒使用還來得久:

compare

內容預告

今天我們介紹了 metadata 的基本概念,明天我們要來介紹一下 ParentDocumentRetriever 這個東西。


上一篇
Day 15 - Chunking Strategy - 使用 LLM 重構段落
下一篇
Day 17 - ParentDocumentRetriever 父文檔檢索器
系列文
初探 Langchain 與 LLM:打造簡易問診機器人30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言